package com.csaba.connector;

import java.util.ArrayList;
import java.util.ServiceLoader;
import java.util.logging.Logger;

import com.csaba.connector.model.Bank;
import com.csaba.util.LoggerHelper;

/**
 * BankServiceFactory is used for instantiating BankServices.
 * <p> BankService classes are defined by banking plugins that can be added dynamically to the Csaba framework.
 * The application is planned to be implemend on the Eclipse's plugin mechanism, but for the time being the java.util.ServiceLoader
 * is used to explore Bank plugins on the class path.
 * <p> Bank plugins has to implement the ServicePluginConfiguration interface that provides information about the plugin, and helps 
 * producing service instancing.
 * <p> There are the following important methods:
 * <dl>
 * <dt>{@link #getAvailableBanks()}</dt><dd>Provides list of banks available as bank plugins.</dd>
 * <dt>{@link #getBankService(Bank, Class)}</dt><dd>Produces a bank service for the selected bank.</dd>
 * </dl>
 * <p> For sample bank plugin implementation see com.csaba.connector.dummy package.
 * @see com.csaba.connector.BankService, com.csaba.connector.ServicePluginConfiguration
 * @author Gabe
 *
 */
public class BankServiceFactory
{ //TODO handling dummy threads to keep session alive
	//TODO handle error message localization
	private final static Logger logger = LoggerHelper.getDefaultLogger();

	private static ServiceLoader<ServicePluginConfiguration> pluginLoader = null;

	private static void initSessionLoader()
	{
		if ( pluginLoader == null )
		{
			pluginLoader = ServiceLoader.load(ServicePluginConfiguration.class);
		}
	}

	/**
	 * Collects list of bank plugins.
	 * @return list of available banks.
	 */
	public static Bank[] getAvailableBanks()
	{
		final ArrayList<Bank> banks = new ArrayList<Bank>();
		initSessionLoader();
		for ( final ServicePluginConfiguration plugin : pluginLoader )
		{
			banks.add(plugin.getBank());
		}
		return banks.toArray(new Bank[banks.size()]);
	}

	/**
	 * Produce a new instance of the required service class for the selected bank.
	 * @param <T> The required Sub interface of the BankService.
	 * @param bank Bank on which the service instance should be produced.
	 * @param serviceClass Class of the service interface. E.g. LoginService.class
	 * @return A new instance of the requested service class.
	 * @throws ServiceException This exception is thrown if any exception occures during the instantiation, or if the service is not supported by the bank.
	 */
	@SuppressWarnings( "unchecked" )
	public static <T extends BankService> T getBankService( final Bank bank, final Class<T> serviceClass )
			throws ServiceException
	{
		initSessionLoader();
		for ( final ServicePluginConfiguration plugin : pluginLoader )
		{
			if ( plugin.getBank().equals(bank) )
			{
				if ( plugin.getServicePackage() != null && plugin.getServicePrefix() != null )
				{
					final String className = plugin.getServicePackage() + "." + plugin.getServicePrefix()
							+ serviceClass.getSimpleName();
					try
					{
						final Class service = Class.forName(className);
						return (T) service.newInstance();
					}
					catch ( final Exception e )
					{
						logger.fine("Automatic class loading failed for " + serviceClass.getName() + " in bank "
								+ bank.getName() + " with exception " + e);
					}
				}
				return (T) plugin.getBankService(serviceClass);
			}
		}
		throw new ServiceException("Unsupported bank code: " + bank.getId());
	}

}
